package com.soywiz.kproject.model

import com.soywiz.kproject.git.*
import com.soywiz.kproject.internal.*
import com.soywiz.kproject.util.*
import org.eclipse.jgit.api.*
import org.eclipse.jgit.lib.*
import java.io.*

data class GitRepository(val repo: String) {
    val httpsRepo = when {
        repo.startsWith("git@") -> "https://" + repo.substringAfter("git@").replace(':', '/')
        else -> repo
    }
    val cachePath: String = PathInfo(httpsRepo.removePrefix("https://").substringAfter("://").replace(':', '/').removeSuffix(".git")).fullPath

    companion object {
        fun fromGithub(org: String, name: String): GitRepository = GitRepository("https://github.com/$org/$name.git")
    }

    fun getCacheFolder(): File = File(getKProjectDir(), "clones/$cachePath")

    inline fun <T> useGit(block: (Git) -> T): T {
        val git = getGit()
        try {
            return block(git)
        } finally {
            git.close()
        }
    }

    @PublishedApi internal fun getGit(): Git {
        val gitFolder = File(getCacheFolder(), "/__git__")

        return when {
            gitFolder.exists() -> Git.open(gitFolder)
            else -> Git.cloneRepository()
                    .setProgressMonitor(TextProgressMonitor(PrintWriter(System.out)))
                    .setURI(httpsRepo)
                    .setDirectory(gitFolder)
                    //.setBare(true)
                    .call()
        }.also {
            it.repository.config
                .setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_AUTOCRLF, false)
        }
    }
}

data class GitRepositoryWithPathAndRef(val repo: GitRepository, val path: String, val ref: String) : Comparable<GitRepositoryWithPathAndRef> {
    data class Content(
        /**
         * [File] with a path to a zip file with the contents
         */
        val zipFile: File,
        /**
         * Provided ref
         */
        val ref: String,
        /**
         * SHA-1 of the [ref] to check for sanity
         */
        val commitId: String,
        /**
         * Hash is the SHA-256 of a ByteArray consisting in all the files in the path with normalized paths (and autocrlf=false):
         *
         * <full/path1>\n<size1>\n<content1><full/path2>\n<size2>\n<content2>...
         *
         * Generated by [generateStableZipContent]
         */
        val hash: String,
        /**
         * Number of commits for this ref
         */
        val commitCount: Int,
    )

    override fun compareTo(other: GitRepositoryWithPathAndRef): Int {
        return this.getContent().commitCount.compareTo(other.getContent().commitCount)
        //return Version(ref).compareTo(Version(other.ref))
    }

    fun getContent(clean: Boolean = false): Content {
        val ref = ref.pathInfo.fullPath
        val gitFolder = repo.getCacheFolder()["__checkouts__"][path.pathInfo.fullPath.trim('/')]
        //println("gitFolder=$gitFolder")
        gitFolder.mkdirs()
        val checkoutZip = gitFolder["$ref.zip"]
        val checkoutJson = gitFolder["$ref.json"]

        if (clean) {
            checkoutZip.delete()
            checkoutJson.delete()
        }

        if (!checkoutZip.exists() || !checkoutJson.exists()) {
            repo.useGit { git ->
                if (!git.checkRefExists(ref)) {
                    git.pull()
                        .setProgressMonitor(TextProgressMonitor(PrintWriter(System.out)))
                        .call()
                }
                if (!git.checkRefExists(ref)) {
                    error("Can't find ref='$ref' in $repo")
                }
                val zipBytes = git.archiveZip(path, ref)
                checkoutZip.writeBytes(zipBytes)
                //println("generateStableZipContent(zipBytes).sha256().str=${generateStableZipContent(zipBytes).sha256().str}")
                checkoutJson.writeText(Json.stringify(
                    mapOf(
                        "contentHash" to generateStableZipContent(zipBytes).sha256().str,
                        "commitId" to git.repository.resolve(ref).name,
                        "commitCount" to git.countCommits(ref).toString()
                    )
                ))
            }
        }
        val info = Json.parse(checkoutJson.readText()).dyn
        return Content(
            zipFile = checkoutZip,
            ref = ref,
            commitId = info["commitId"].str,
            hash = info["contentHash"].str,
            commitCount = info["commitCount"].int
        )
    }
}
